#include "cst816d.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "st7789.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define ST7789_CLK_PIN  2
#define ST7789_MOSI_PIN 3
#define ST7789_CS_PIN   10
#define ST7789_RST_PIN  6
#define ST7789_DC_PIN   7
#define ST7789_BL_PIN   11

static spi_device_handle_t spi;

void esp32c3_spi_write(uint8_t* bytes, uint32_t len)
{
	uint32_t section = 4096;
	spi_transaction_t t;

	if (!len) {
		return;
	}

	while (len > section) {
		memset(&t, 0, sizeof(t));             // Zero out the transaction
		t.length = 8 * section;               // Command is 8 bits
		t.tx_buffer = bytes;                  // The data is the cmd itself
		spi_device_polling_transmit(spi, &t); // Transmit!
		bytes += section;
		len -= section;
	}

	memset(&t, 0, sizeof(t));             // Zero out the transaction
	t.length = 8 * len;                   // Command is 8 bits
	t.tx_buffer = bytes;                  // The data is the cmd itself
	spi_device_polling_transmit(spi, &t); // Transmit!
}

static void esp32c3_rst_pin(int val)
{
	gpio_set_level(ST7789_RST_PIN, val);
}

static void esp32c3_dc_pin(int val)
{
	gpio_set_level(ST7789_DC_PIN, val);
}

static void esp32c3_bl_pin(int val)
{
	gpio_set_level(ST7789_BL_PIN, val);
}

static void esp32c3_msleep(int ms)
{
	usleep(1000 * ms);
}

#define CST816D_SCL_PIN 5
#define CST816D_SDA_PIN 4
#define CST816D_RST_PIN 8
#define CST816D_INT_PIN 9

#define I2C_MASTER_TX_BUF_DISABLE 0   /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0   /*!< I2C master doesn't need buffer */
#define ACK_CHECK_EN              0x1 /*!< I2C master will check ack from slave*/
#define ACK_CHECK_DIS             0x0 /*!< I2C master will not check ack from slave */
#define ACK_VAL                   0x0 /*!< I2C ack value */
#define NACK_VAL                  0x1 /*!< I2C nack value */

static i2c_port_t i2c_port = I2C_NUM_0;
static bool flag = 1;

static int esp32c3_tp_i2c_read(uint8_t reg, uint8_t* bytes, uint32_t len)
{
	int ret = 0;
	i2c_cmd_handle_t cmd;

	cmd = i2c_cmd_link_create();
	ret = i2c_master_start(cmd);
	if (ret != ESP_OK) {
		goto end;
	}

	ret = i2c_master_write_byte(cmd, CST816D_I2C_7BIT_ADDR << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN);
	if (ret != ESP_OK) {
		goto end;
	}

	ret = i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
	if (ret != ESP_OK) {
		goto end;
	}

	ret = i2c_master_start(cmd);
	if (ret != ESP_OK) {
		goto end;
	}

	ret = i2c_master_write_byte(cmd, CST816D_I2C_7BIT_ADDR << 1 | I2C_MASTER_READ, ACK_CHECK_EN);
	if (ret != ESP_OK) {
		goto end;
	}

	ret = i2c_master_read(cmd, bytes, len - 1, ACK_VAL);
	if (ret != ESP_OK) {
		goto end;
	}

	ret = i2c_master_read_byte(cmd, bytes + len - 1, NACK_VAL);
	if (ret != ESP_OK) {
		goto end;
	}

	ret = i2c_master_stop(cmd);
	if (ret != ESP_OK) {
		goto end;
	}

	ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_PERIOD_MS);
	if (ret == ESP_ERR_TIMEOUT) {
		printf("CST816D Bus is busy\n");
		goto end;
	} else if (ret != ESP_OK) {
		printf("CST816D Read Failed\n");
		goto end;
	}

end:
	i2c_cmd_link_delete(cmd);

	return ret;
}

static int esp32c3_tp_i2c_write(uint8_t reg, uint8_t* bytes, uint32_t len)
{
	int ret = 0;
	i2c_cmd_handle_t cmd;

	cmd = i2c_cmd_link_create();
	ret = i2c_master_start(cmd);
	if (ret != ESP_OK) {
		goto end2;
	}

	ret = i2c_master_write_byte(cmd, CST816D_I2C_7BIT_ADDR << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN);
	if (ret != ESP_OK) {
		goto end2;
	}

	ret = i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
	if (ret != ESP_OK) {
		goto end2;
	}

	for (int i = 0; i < len; i++) {
		ret = i2c_master_write_byte(cmd, bytes[i], ACK_CHECK_EN);
		if (ret != ESP_OK) {
			goto end2;
		}
	}

	ret = i2c_master_stop(cmd);
	if (ret != ESP_OK) {
		goto end2;
	}

	ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_PERIOD_MS);
	if (ret == ESP_ERR_TIMEOUT) {
		printf("CST816D Bus is busy\n");
		goto end2;
	} else if (ret != ESP_OK) {
		printf("CST816D Write Failed\n");
		goto end2;
	}

end2:
	i2c_cmd_link_delete(cmd);

	return ret;
}

static void esp32c3_tp_rst_pin(int val)
{
	gpio_set_level(CST816D_RST_PIN, val);
}

static void IRAM_ATTR gpio_isr_handler(void* arg)
{
	flag = 1;
}

static void board_init()
{
	int ret = 0;

	/* gpio */
	gpio_reset_pin(ST7789_RST_PIN);
	gpio_set_direction(ST7789_RST_PIN, GPIO_MODE_OUTPUT);

	gpio_reset_pin(ST7789_DC_PIN);
	gpio_set_direction(ST7789_DC_PIN, GPIO_MODE_OUTPUT);

	gpio_reset_pin(ST7789_BL_PIN);
	gpio_set_direction(ST7789_BL_PIN, GPIO_MODE_OUTPUT);

	/* spi */
	spi_bus_config_t buscfg = {
		.miso_io_num = -1,
		.mosi_io_num = ST7789_MOSI_PIN,
		.sclk_io_num = ST7789_CLK_PIN,
		.quadwp_io_num = -1,
		.quadhd_io_num = -1,
		.max_transfer_sz = 4096,
	};

	spi_device_interface_config_t devcfg = {
		.clock_speed_hz = 80000000,    // SPI clk
		.mode = 0,                     // SPI mode
		.spics_io_num = ST7789_CS_PIN, // CS pin
		.queue_size = 7,               // We want to be able to queue 7 transactions at a time
	};

	spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
	spi_bus_add_device(SPI2_HOST, &devcfg, &spi);

	/* gpio */
	gpio_reset_pin(CST816D_RST_PIN);
	gpio_set_direction(CST816D_RST_PIN, GPIO_MODE_OUTPUT);

	/* interrupt */
	gpio_reset_pin(CST816D_INT_PIN);
	gpio_set_direction(CST816D_INT_PIN, GPIO_MODE_INPUT);
	gpio_set_intr_type(CST816D_INT_PIN, GPIO_INTR_NEGEDGE);
	gpio_install_isr_service(0);
	gpio_isr_handler_add(CST816D_INT_PIN, gpio_isr_handler, (void*)CST816D_INT_PIN);

	/* i2c */
	i2c_config_t conf = {
		.mode = I2C_MODE_MASTER,
		.sda_io_num = CST816D_SDA_PIN,
		.sda_pullup_en = GPIO_PULLUP_ENABLE,
		.scl_io_num = CST816D_SCL_PIN,
		.scl_pullup_en = GPIO_PULLUP_ENABLE,
		.master.clk_speed = 400000,
	};

	i2c_driver_install(i2c_port, I2C_MODE_MASTER, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
	ret = i2c_param_config(i2c_port, &conf);
	if (ret) {
		printf("err: i2c_param_config(%d, conf), ret = %d\n", i2c_port, ret);
	}
}

void app_main(void)
{
	struct st7789 lcd;
	static struct cst816d tp;

	int ret = 0;
	bool pressed = 0;
	uint16_t x = 0;
	uint16_t y = 0;
	int dot = 1;

	/* esp32c3 drivers init */
	board_init();

	/* set lcd param */
	lcd.cfg.fix.width = 240;
	lcd.cfg.fix.height = 280;
	lcd.cfg.fix.xshift = 0;
	lcd.cfg.fix.yshift = 20;
	lcd.cfg.fix.bits = 8;
	lcd.cfg.var.orien = 0;

	/* register the operation */
	lcd.ops.reset = esp32c3_rst_pin;
	lcd.ops.datacmd = esp32c3_dc_pin;
	lcd.ops.backlight = esp32c3_bl_pin;
	lcd.ops.msleep = esp32c3_msleep;
	lcd.ops.write = esp32c3_spi_write;
	st7789_init(&lcd);

	/* set tp param */
	tp.cfg.fix.width = 240;
	tp.cfg.fix.height = 280;

	/* register tp operation */
	tp.ops.read = esp32c3_tp_i2c_read;
	tp.ops.write = esp32c3_tp_i2c_write;
	tp.ops.msleep = esp32c3_msleep;
	tp.ops.reset = esp32c3_tp_rst_pin;

	/* cst816d touch panel api test */
	cst816d_init(&tp);

	/* white background */
	st7789_rect_fill_color(&lcd, 0, 0, lcd.cfg.fix.width - 1, lcd.cfg.fix.height - 1, RGB565_WHITE);

	while (1) {
		if (flag) {
			/* get coord */
			ret = cst816d_get_coord(&tp, &pressed, &x, &y);
			if (0 == ret) {
				/* read coord successful, then lcd dram point */
				printf("tp: (%d, %d)\n", x, y);
				st7789_rect_fill_color(&lcd, x, y, x + dot, y + dot, RGB565_RED);
			}
			flag = 0;
		} else {
			vTaskDelay(10 / portTICK_PERIOD_MS);
		}
	}
}
